访问修饰符
访问修饰符 | 同一个类 | 同包 | 不同包,子类 | 不同包,非子类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
Java中对象及对象引用变量
Java中的对象的引用和对象有些类似C++中的地址和地址的指向的关系。1
2
3
4
5public class Information {
int name;
int grade;
int point;
}
我们先定义一个Information类。
然后我们创建一个Infromation对象:1
Information stu1 = new Information();
这个操作的本质是:
new Information
是以Information类为模板,在堆空间里创建一个Information类对象。- 末尾的()意味着,在对象创建后,立即调用Information类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
Information stu1
是创建一个Information类引用变量。=
使对象引用指向刚创建的那个Information对象。
我们可以把stu1类比于C++中的地址。1
Information stu2 = stu1;
此时stu2和stu1都指向刚创建的那个Information对象。1
2Information stu2 = new Information();
stu1 = stu2;
这样之前的那个Information类就会被回收。
Integer和int
Java的两种数据类型:
- 基本类型:基本数据类类型存的是数值本身。有byte, short, int, long, float, double, boolean, char。
- 引用类型:引用类型变量在内存放的是数据的引用。比如上面定义的Information,以及标题提及的Integer。
下面我们重点讨论”==”这个运算符。
先给出如下表达式的值:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16int a = 1000;
int b = 1000;
Integer c = 10;
Integer d = 10;
Integer e = 1000;
Integer f = 1000;
Integer g = new Integer(10);
Integer h = new Integer(10);
System.out.println(a == b); //true
System.out.println(c == d); //true
System.out.println(c.equals(d)); //true
System.out.println(e == f); //false
System.out.println(e.equals(f)); //true
System.out.println(g == h); //false
System.out.println(g.equals(h)); // true
System.out.println(a == e); //true
int是基本类型,他的 == 比较是基于数值本身的。
Integer是引用类型,本质是个类,因此它还有equals()
函数可以进行比较,也可以用 == 来进行比较。equals()
比较是基于数值的; 而Integer
的 == 比较是基于引用地址的。
现在又有一个问题,为什么c == d
是true呢?
原因是这样的:
当我们给一个Integer赋予一个int类型的时候会调用Integer的静态方法valueOf()
。也就是说,Integer c = 10;相当于Integer c = Integer.valueOf(10);
jdk1.5的时候引进了自动拆包,装包这个功能。上面这种情况就是自动打包出现的。
具体来看看Integer.valueOf()
的源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/*
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* jdk.internal.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
从上面我们可以知道给Interger
赋予的int
数值在-128 - 127的时候,直接从cache中获取,这些cache引用对Integer
对象地址是不变的,但是不在这个范围内的数字,则new Integer(i)
这个地址是新的地址,不可能一样的。
所以上个例子中c和d都是IntegerCache的引用,故c == d
为true。而cache中没有1000,故e == f
为false。
而int
和Integer
比较时,Integer
会自动拆包,转化为int和其比较。
final关键字
- 被final修饰的类不可以被继承,final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
- 被final修饰的方法不可以被重写
- 被final修饰基本数据类型和String类型的变量为常量,不可以被修改,且在编译阶段会存入调用类的常量池中
- 被final修饰的引用类型变量不可以改变引用的指向,指向的对象的内容是可以改变的
1 | public class test { |
输出为1
21 2 3
100 2 3
static关键字(待重新整理)
首先说this。
this首先是一个对象,它代表调用这个函数的对象。
根据面向对象的基本语法,每当调用变量或者函数的时候,都要按照类名.变量(函数) 的格式来调用。
在不会产生混淆的地方, this是可以省略的。1
2
3
4
5
6
7
8
9
10
11class Name {
String name;
void QAQ() {
System.out.println(this.name);
}
}
/*
定义Name n1, n2;
对于n1.QAQ(),this.name为n1.name。
对于n2.QAQ(),this.name为n2.name。
*/
static 方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是可以定义一个对象,通过对象来访问。
抽象方法和抽象类
一个类没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
当你想构造一个父类方法,他的具体实现由子类来定义,那么这个方法可以定义为抽象方法,当然,普通方法也可以。
抽象类和抽象方法用abstrct
来修饰。
抽象类和方法的性质:
抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用
static
修饰的方法)不能声明为抽象方法。抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
抽象方法必须为public或者protected,缺省默认public。
1 | abstract class func { |
其中function3()会有一个warning:
The method function3() from the type func is never used locally
再建立一个func2类继承于func:1
2class func2 extends func {
}
会有一个error:
The type func2 must implement the inherited abstract method func.function1()
就是说我们必须重写function1()。
重写:1
2
3
4
5
6
7
8class func2 extends func {
void function1() {
System.out.println("1 in func2");
}
void function2() {
System.out.println("2 in func2");
}
}
定义:1
2
3
4
5func2 f = new func2();
f.function1();
f.function2();
//f.function3(); ERROR
f.function4();
输出:1
2
31 in func2
2 in func2
4 in func1
抽象类和接口
接口(interface) 在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。
接口的定义方式如下:1
2interface InterfaceName {
}
接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final
变量(final类型详见第4节),而方法会被隐式地指定为public abstract
方法。
要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:1
2class ClassName implements Interface1,Interface2,...{
}
另外,一个接口必须声明在同名的.java中。
同时,接口还可以继承接口。
实例:
test.java1
2
3
4
5
6
7
8package test;
interface test {
void f1();
void f2();
void f3();
}
test2.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package test;
public interface test2 extends test{
int a = 10;
abc b = new abc();
void f4();
}
class abc {
int len = 5;
void a() {
System.out.println("a");
}
void b() {
System.out.println("b");
}
void c() {
System.out.println("c");
}
}
Test3.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package test;
public class Test3 implements test2 {
/*
* f1到f4都是test2接口的实现
*/
public void f1() {
System.out.println("1");
}
public void f2() {
System.out.println("2");
}
public void f3() {
System.out.println("3");
}
public void f4() {
System.out.println("4");
}
public static void main(String[] args) {
Test3 t = new Test3();
/*
* a被final修饰
* 若a = 100; 会报错The final field test2.a cannot be assigned
*/
System.out.println(a);
/*
* b是被final修饰的一个类。
* final类中的变量不是不被final修饰,而方法被隐性转为final。
* 因此b.len可以被改变。
*/
b.len = 1;
System.out.println(b.len);
/*三个接口中的变量的方法*/
b.a();b.b();b.c();
/*
* 四个接口方法
*/
t.f1();
t.f2();
t.f3();
t.f4();
}
}
总的来说,抽象类和接口的区别有:抽象类是一个类,里边可以包含抽象方法和非抽象方法、抽象变量和非抽象变量,而接口是方法的集合,只包含方法。一个类可以有多个接口,但只能有一个父类。
可变对象和不可变对象
概念
不可变对象(Immutable Objects):对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,任何对它的改变都应该产生一个新的对象。比如String。
可变对象(Mutable Objects):相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。比如StringBuilder。
如何构造不可变类
- 类添加final修饰符,保证类不被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。 - 保证所有成员变量必须私有,并且加上final修饰。
通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。 - 不提供改变成员变量的方法,包括setter。
避免通过其他接口改变成员变量的值,破坏不可变特性。 - 通过构造器初始化所有成员,进行深拷贝(deep copy)。
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:1
2
3
4
5
6public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}